﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.Xml.Linq;

namespace CraigsCrawler
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private static MainWindow instance;

        private Thread workerThread;
        private List<SearchStorage> searches = new List<SearchStorage>();
        private IEnumerable<Entry> entries = Enumerable.Empty<Entry>();

        public MainWindow()
        {
            instance = this;
            InitializeComponent();
            workerThread = new Thread(() => Work());
            workerThread.Start();
        }

        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            base.OnClosing(e);

            WriteLine("Shutting down worker thread...");
            workerThread.Join();
            Querier.Current.Shutdown();
            searches.ForEach(s => s.Save());
        }

        public SearchStorage SelectedSearch { get; set; }

        public static void WriteLine(string line)
        {
            Application.Current.Dispatcher.Invoke(() =>
                {
                    instance.StatusBox.AppendText(DateTime.Now.ToShortTimeString() + " " + line + "\n");
                    instance.StatusBox.ScrollToEnd();
                });
        }

        public static void WriteException(string line, Exception ex)
        {
            WriteLine(line);
            if (null == ex) return;
            WriteLine(ex.Message);
        }

        private void RefreshList()
        {
            Application.Current.Dispatcher.Invoke(() =>
                {
                    ItemPanel.Items.Clear();

                    foreach (Entry entry in entries)
                    {
                        ItemPanel.Items.Add(new EntryControl(entry));
                    }
                });
        }

        private void Work()
        {
            searches.AddRange(SearchStorage.LoadAll());
            Application.Current.Dispatcher.Invoke(() =>
                {
                    SearchSelector.Items.Clear();
                    searches.ForEach(s => SearchSelector.Items.Add(s));
                    SearchSelector.Items.Add(new NamedThing() { Name = "Edit..." });
                    SearchSelector.Items.Add(new NamedThing() { Name = "Add..." });
                    if (searches.Count > 0)
                    {
                        SelectedSearch = (SearchStorage)SearchSelector.Items[0];
                        SearchSelector.SelectedIndex = 0;
                    }
                });

            Querier.Finisher = () => LoadCities();

            LoadCities();
            LoadMissedThumbs();

            if (searches.Count > 0)
            {
                NewButton_Click(null, null);
            }
        }

        private void LoadCities()
        {
            IEnumerable<Tuple<string, SearchStorage>> shuffledCities = searches
                .SelectMany(s => s.cities.Select(c => Tuple.Create(c, s)))
                .OrderBy(c => Guid.NewGuid())
                .ToList();

            foreach (Tuple<string, SearchStorage> city in shuffledCities)
            {
                string queryCity = Utilities.GetCity(city.Item2.query);
                string query = city.Item2.query.Replace("http://" + queryCity, "http://" + city.Item1);
                Querier.Current.QueueRequest(query, null, (u, c) => ProcessedCity(city.Item2, u, c), (u, e) => MainWindow.WriteException("Failed city " + u, e));
            }

            MainWindow.WriteLine("Queued " + shuffledCities.Count() + " cities");
        }

        private void LoadMissedThumbs()
        {
            int added = 0;
            foreach (Entry entry in searches.SelectMany(s => s.entries))
            {
                if (null == entry.thumbnail)
                {
                    Querier.Current.QueueRequest(
                        entry.link,
                        entry,
                        (u, c) => ProcessedPage(entry, u, c),
                        (u, e) => MainWindow.WriteException("Failed page " + u, e));

                    added++;
                }
            }

            MainWindow.WriteLine("Queued " + added + " missing thumbnails");
        }

        private void ProcessedCity(SearchStorage search, string url, Stream content)
        {
            string city = Utilities.GetCity(url);
            string status = "Retrieved " + city;

            try
            {
                List<Entry> entries = Parse.RssForEntries(search, url, content).ToList();

                status += ", " + entries.Count + " entries";

                IEnumerable<Entry> duplicateByUrl = entries.Where(e => search.entriesByUrl.ContainsKey(e.link));

                int added = 0;
                int ignored = 0;
                foreach (Entry entry in entries)
                {
                    if (search.entriesByUrl.ContainsKey(entry.link))
                    {
                        Entry existing = search.entriesByUrl[entry.link];
                        existing.title = entry.title;
                        existing.posted = entry.posted;
                        existing.lastRetrieved = entry.lastRetrieved;
                    }
                    else
                    {
                        if (search.suppressedTitles.Any(t => t.Item1 == entry.title && t.Item2 == entry.city))
                        {
                            ignored++;
                            entry.entryState = EntryState.AutomaticallyIgnored;
                        }
                        else
                        {
                            added++;
                            Querier.Current.QueueRequest(
                                entry.link,
                                entry,
                                (u, c) => ProcessedPage(entry, u, c),
                                (u, e) => MainWindow.WriteException("Failed page " + u, e),
                                url);
                        }

                        search.entries.Add(entry);
                        search.entriesByUrl.Add(entry.link, entry);
                    }
                }

                status += ", " + added + " new, " + ignored + " ignored";

                if (added > 0)
                {
                    search.Save();
                }
            }
            finally
            {
                MainWindow.WriteLine(status);
            }
        }

        private void ProcessedPage(Entry entry, string url, Stream content)
        {
            MainWindow.WriteLine("Page " + entry.title);

            string thumbLink = Parse.PageForThumb(entry, url, content);

            if (string.IsNullOrEmpty(thumbLink))
            {
                entry.thumbnail = thumbLink;
            }
            else
            {
                Querier.Current.RequestImmediately(thumbLink, (u, c) => ProcessedThumb(entry, u, c), (u, e) => MainWindow.WriteException("Failed thumb " + u, e), url);
            }
        }

        private void ProcessedThumb(Entry entry, string url, Stream content)
        {
            try
            {
                Uri uri = new Uri(url);
                string fileName = uri.Segments.Last();
                MainWindow.WriteLine("Thumbnail " + fileName);
                using (StreamWriter writer = new StreamWriter(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CraigsCrawler", entry.parent.Name, fileName), false))
                {
                    content.CopyTo(writer.BaseStream);
                }
                entry.thumbnail = fileName;

                if (entry.control != null)
                {
                    entry.control.AddThumbnail(entry.thumbnail);
                }
            }
            catch
            {
                entry.thumbnail = string.Empty;
                throw;
            }
        }

        private void ItemPanel_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            EntryControl control = e.AddedItems.Cast<EntryControl>().FirstOrDefault();

            if (null != control)
            {
                Browser.Navigate(control.Entry.link);
            }
        }

        private void NewButton_Click(object sender, RoutedEventArgs ev)
        {
            entries = SelectedSearch.entries.Where(e => e.entryState == EntryState.New);
            RefreshList();
        }

        private void InterestedButton_Click(object sender, RoutedEventArgs ev)
        {
            entries = SelectedSearch.entries.Where(e => e.entryState == EntryState.Interested);
            RefreshList();
        }

        private void IgnoredButton_Click(object sender, RoutedEventArgs ev)
        {
            entries = SelectedSearch.entries.Where(e => e.entryState == EntryState.AutomaticallyIgnored || e.entryState == EntryState.ManuallyIgnored);
            RefreshList();
        }

        public void ProcessedScrub(Entry entry, string url, Stream content)
        {
            StreamReader reader = new StreamReader(content);
            string text = reader.ReadToEnd();
            MemoryStream stream = new MemoryStream();
            StreamWriter writer = new StreamWriter(stream);
            writer.Write(text);

            if (!Parse.PageForRemoved(stream))
            {
                stream.Seek(0, SeekOrigin.Begin);

                entry.lastRetrieved = DateTime.UtcNow;

                //
                // Success
                //
                if (string.IsNullOrEmpty(entry.thumbnail))
                {
                    string thumbLink = Parse.PageForThumb(entry, url, stream);

                    if (!string.IsNullOrEmpty(thumbLink))
                    {
                        Querier.Current.RequestImmediately(thumbLink, (u, c) => ProcessedThumb(entry, u, c), (u, e) => MainWindow.WriteException("Failed thumb " + u, e), url);
                    }
                }

                WriteLine("Scrubbed " + entry.title);
            }
            else
            {
                //
                // Failure
                //
                if (!string.IsNullOrEmpty(entry.thumbnail))
                {
                    try
                    {
                        File.Delete(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CraigsCrawler", entry.thumbnail));
                    }
                    catch
                    {
                        WriteLine("Could not delete thumbnail " + entry.thumbnail);
                    }
                }

                SelectedSearch.entriesByUrl.Remove(entry.link);
                SelectedSearch.entries.Remove(entry);

                WriteLine("Removed " + entry.city + " " + entry.title);
            }
        }

        private void SearchSelector_SelectionChanged(object sender, RoutedEventArgs e)
        {
            SearchStorage trySelectWhenDone = SelectedSearch;
            if (SearchSelector.SelectedItem == null)
            {
                return;
            }
            else if (SearchSelector.SelectedItem is SearchStorage)
            {
                SelectedSearch = (SearchStorage)SearchSelector.SelectedItem;
                NewButton_Click(null, null);
                return;
            }
            else if (((NamedThing)SearchSelector.SelectedItem).Name == "Add...")
            {
                EditSearchStorage window = new EditSearchStorage(this, null);
                window.ShowDialog();
                if (window.Storage != null)
                {
                    searches.Add(window.Storage);
                    trySelectWhenDone = window.Storage;
                    LoadCities();
                }
            }
            else if (((NamedThing)SearchSelector.SelectedItem).Name == "Edit...")
            {
                if (SelectedSearch != null)
                {
                    EditSearchStorage window = new EditSearchStorage(this, SelectedSearch);
                    window.ShowDialog();
                    if (window.Storage == null)
                    {
                        searches.Add(window.Storage);
                        LoadCities();
                    }
                }
            }

            List<SearchStorage> temp = new List<SearchStorage>(searches);
            searches.Clear();
            searches.AddRange(temp.OrderBy(s => s.Name));
            SearchSelector.Items.Clear();
            searches.ForEach(s => SearchSelector.Items.Add(s));
            SearchSelector.Items.Add(new NamedThing() { Name = "Edit..." });
            SearchSelector.Items.Add(new NamedThing() { Name = "Add..." });
            if (searches.Count > 0)
            {
                if (null != trySelectWhenDone && searches.Contains(trySelectWhenDone))
                {
                    SelectedSearch = trySelectWhenDone;
                    SearchSelector.SelectedItem = trySelectWhenDone;
                }
                else
                {
                    SelectedSearch = (SearchStorage)SearchSelector.Items[0];
                    SearchSelector.SelectedIndex = 0;
                }
            }
        }
    }
}
